/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.log;


import cn.easyplatform.dos.UserDo;
import cn.easyplatform.interceptor.CommandContext;
import org.apache.commons.io.FilenameUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender;
import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.action.*;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.filter.ThreadContextMapFilter;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.util.KeyValuePair;

import java.io.File;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import java.util.*;

/**
 * @Author: <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @Description:
 * @Since: 2.0.0 <br/>
 * @Date: Created in 2019/11/5 16:23
 * @Modified By:
 */
public final class LogManager {

    private final static String LOGGER_NAME = "cn.easyplatform";

    private final static String APP_APPENDER = "APP";

    private final static String USER_APPENDER = "USER";

    private static final LoggerContext CONTEXT = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);

    private static final Configuration CONFIGURATION = CONTEXT.getConfiguration();

    private static String appBaseSize;

    private static String appMaxRollover;

    private static String appLastModified;

    private static String userBaseSize;

    private static String userMaxRollover;

    private static String userLastModified;


    /**
     * 初始配置
     *
     * @param section
     */
    public static void setConfiguration(Map<String, String> section) {
        appBaseSize = section.getOrDefault("app.size.based", "10 MB");
        appMaxRollover = section.getOrDefault("app.max.rollover", "30");
        appLastModified = section.getOrDefault("app.last.modified", "30d");
        userBaseSize = section.getOrDefault("user.size.based", "10 MB");
        userMaxRollover = section.getOrDefault("user.max.rollover", "30");
        userLastModified = section.getOrDefault("user.last.modified", "30d");
    }

    /**
     * 启动项目日志
     * 以${项目id}作为主id,以${项目id}作为过滤元素，只记录本项目的日志
     *
     * @param id 项目id
     */
    public final static void startApp(String logPath, String id) {
        if (CONFIGURATION.getAppender(id) != null)
            return;

        StringBuilder sb = new StringBuilder();
        sb.append(logPath).append("/").append(id);
        File dir = new File(sb.toString());
        if (!dir.exists())//创建项目对应的目录
            dir.mkdir();
        sb.append("/").append("apps.log");
        String fileName = FilenameUtils.normalize(sb.toString());
        sb.setLength(0);
        sb.append(logPath).append("/").append(id).append("/").append("apps-%d{yyyy-MM-dd}-%i.log.gz");
        String filePattern = FilenameUtils.normalize(sb.toString());
        final PatternLayout layout = PatternLayout.newBuilder()
                .withCharset(Charset.forName("UTF-8"))
                .withConfiguration(CONFIGURATION)
                .withPattern("[%d{HH:mm:ss:SSS}] %p %c{1} - %m%n")
                .build();
        final RolloverStrategy strategy = DefaultRolloverStrategy.newBuilder().withMax(appMaxRollover).withCustomActions(
                new Action[]{DeleteAction.createDeleteAction(FilenameUtils.normalize(logPath + "/" + id), false, 1, false, null, new PathCondition[]{IfFileName.createNameCondition("apps-*.log.gz", "apps-*.log.gz"), IfLastModified.createAgeCondition(Duration.parse(appLastModified))}, null, CONFIGURATION)}).withConfig(CONFIGURATION).build();
        final TriggeringPolicy policy = SizeBasedTriggeringPolicy.createPolicy(appBaseSize);
        final Appender appender = RollingRandomAccessFileAppender.newBuilder()
                .setName(id)
                .withImmediateFlush(true)
                .withFileName(fileName)
                .withFilePattern(filePattern)
                .setLayout(layout)
                .withPolicy(policy).withStrategy(strategy)
                .build();
        appender.start();
        CONFIGURATION.addAppender(appender);
        final KeyValuePair[] pairs = {KeyValuePair.newBuilder().setKey(APP_APPENDER).setValue(id).build()};
        final Filter filter = ThreadContextMapFilter.createFilter(pairs, null, Filter.Result.ACCEPT, Filter.Result.DENY);
        final LoggerConfig loggerConfig = CONFIGURATION.getLoggerConfig(LOGGER_NAME);
        loggerConfig.addAppender(appender, loggerConfig.getLevel(), filter);
        CONTEXT.updateLoggers(CONFIGURATION);
        ThreadContext.put(APP_APPENDER, id);
    }

    /**
     * 停止项目日志
     *
     * @param id
     */
    public final static void stopApp(String id) {
        removeAppender(id);
    }

    /**
     * 用户登陆成功后开始添加appender,包括后台计划任务的日志也使用此接口，务必保证参数job的id不能与现有的用户id一样
     * <p>
     * 以${sessionId}作为主id,以${projectId}+${userId}作为过滤元素，只记录用户本身的日志
     * </p>
     *
     * @param projectId
     * @param user
     */
    public final static void startUser(String logPath, String projectId, UserDo user) {
        String id = (String) user.getSessionId();
        if (CONFIGURATION.getAppender(id) != null)
            return;

        StringBuilder sb = new StringBuilder(100);
        sb.append(logPath).append("/").append(projectId).append("/").append(user.getId()).append("/").append("app.log");
        String fileName = FilenameUtils.normalize(sb.toString());
        sb.setLength(0);
        sb.append(logPath).append("/").append(projectId).append("/").append(user.getId()).append("/app-%d{yyyy-MM-dd}-%i.log.gz");
        String filePattern = FilenameUtils.normalize(sb.toString());
        final PatternLayout layout = PatternLayout.newBuilder()
                .withCharset(Charset.forName("UTF-8"))
                .withConfiguration(CONFIGURATION)
                .withPattern("[%d{HH:mm:ss:SSS}] %p %c{1} - %m%n")
                .build();
        final RolloverStrategy strategy = DefaultRolloverStrategy.newBuilder().withMax(userMaxRollover).withCustomActions(
                new Action[]{DeleteAction.createDeleteAction(FilenameUtils.normalize(logPath + "/" + projectId + "/" + user.getId()), false, 1, false, null, new PathCondition[]{IfFileName.createNameCondition("app-*.log.gz", "app-*.log.gz"), IfLastModified.createAgeCondition(Duration.parse(userLastModified))}, null, CONFIGURATION)}).withConfig(CONFIGURATION).build();
        final TriggeringPolicy policy = SizeBasedTriggeringPolicy.createPolicy(userBaseSize);
        final Appender appender = RollingRandomAccessFileAppender.newBuilder()
                .setName(id)
                .withImmediateFlush(true)
                .withFileName(fileName)
                .withFilePattern(filePattern)
                .setLayout(layout)
                .withPolicy(policy).withStrategy(strategy)
                .build();
        appender.start();
        CONFIGURATION.addAppender(appender);
        sb.setLength(0);
        final KeyValuePair[] pairs = {KeyValuePair.newBuilder().setKey(USER_APPENDER).setValue(sb.append(projectId).append(user.getId()).toString()).build()};
        final Filter filter = ThreadContextMapFilter.createFilter(pairs, null, Filter.Result.ACCEPT, Filter.Result.DENY);
        final LoggerConfig loggerConfig = CONFIGURATION.getLoggerConfig(LOGGER_NAME);
        loggerConfig.addAppender(appender, loggerConfig.getLevel(), filter);
        CONTEXT.updateLoggers(CONFIGURATION);
    }

    /**
     * 用户退出时移除appender
     *
     * @param user
     */
    public final static void stopUser(UserDo user) {
        if (user != null) {
            removeAppender((String) user.getSessionId());
            removeAppender(user.getSessionId() + "-Console");
        }
    }

    /**
     * 每个请求开始初始化MDC
     *
     * @param projectId
     * @param user
     */
    public final static void beginRequest(String projectId, UserDo user) {
        ThreadContext.put(APP_APPENDER, projectId);
        if (user != null)
            ThreadContext.put(USER_APPENDER, projectId + user.getId());
    }

    /**
     * 每个请求结束后移除MDC
     */
    public final static void endRequest() {
        ThreadContext.remove(APP_APPENDER);
        ThreadContext.remove(USER_APPENDER);
    }

    /**
     * 客户请求显示动态日志
     * 以登陆用户的${sessionId}+"-Console"作为appender的id
     *
     * @param type
     * @param level
     * @param cc
     * @param filterId 根据type确定过滤条件
     */
    public final static void startConsole(ConsoleType type, Level level, CommandContext cc, String filterId) {
        String id = cc.getUser().getSessionId() + "-Console";
        Appender appender = CONFIGURATION.getAppender(id);
        if (appender != null)//如果存在先移除
            removeAppender(id);

        KeyValuePair.Builder builder = KeyValuePair.newBuilder();
        if (type == ConsoleType.APP)
            builder.setKey(APP_APPENDER).setValue(filterId);
        else
            builder.setKey(USER_APPENDER).setValue(cc.getEnv().getId() + filterId);

        final Filter filter = ThreadContextMapFilter.createFilter(new KeyValuePair[]{builder.build()}, null, Filter.Result.ACCEPT, Filter.Result.DENY);
        if (cc.getProjectService() == null)
            appender = new ConsoleAppender(id, filter, null, false, null).build(cc, filterId);
        else
            appender = new ConsoleAppender(id, filter, null, false, null).build(cc);
        appender.start();
        CONFIGURATION.addAppender(appender);
        final LoggerConfig loggerConfig = CONFIGURATION.getLoggerConfig(LOGGER_NAME);
        loggerConfig.addAppender(appender, level, filter);
        CONTEXT.updateLoggers(CONFIGURATION);
    }

    /**
     * 用户端结束日志
     *
     * @param user
     */
    public final static void stopConsole(UserDo user) {
        removeAppender(user.getSessionId() + "-Console");
    }

    /**
     * 移除appender
     *
     * @param id
     */
    private static synchronized void removeAppender(String id) {
        Appender appender = CONFIGURATION.getAppender(id);
        if (appender != null) {
            //延时30秒关闭
            if (appender instanceof RollingRandomAccessFileAppender)
                ((RollingRandomAccessFileAppender) appender).stop(30, TimeUnit.SECONDS);
            else
                appender.stop();
            CONFIGURATION.getAppenders().remove(id);
            CONFIGURATION.getLoggerConfig(LOGGER_NAME).removeAppender(id);
        }
    }
}
